/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.sunspotworld.executor;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * This class provides command line operation
 * @author Qing An - qinga@kth.se
 * Condroid Summer CSD 2011 - KTH
 */
public class CommandExecutor {

    private ILogDevice fOuputLogDevice = null;
    private ILogDevice fErrorLogDevice = null;
    private String fWorkingDirectory = null;
    private List fEnvironmentVarList = null;
    private StringBuffer fCmdOutput = null;
    private StringBuffer fCmdError = null;
    private AsyncStreamReader fCmdOutputThread = null;
    private AsyncStreamReader fCmdErrorThread = null;

    /**
     * 
     * @param logDevice
     */
    public void setOutputLogDevice(ILogDevice logDevice) {
        fOuputLogDevice = logDevice;
    }

    /**
     * 
     * @param logDevice
     */
    public void setErrorLogDevice(ILogDevice logDevice) {
        fErrorLogDevice = logDevice;
    }

    /**
     * 
     * @param workingDirectory
     */
    public void setWorkingDirectory(String workingDirectory) {
        fWorkingDirectory = workingDirectory;
    }

    /**
     * 
     * @param name
     * @param value
     */
    public void setEnvironmentVar(String name, String value) {
        if (fEnvironmentVarList == null) {
            fEnvironmentVarList = new ArrayList();
        }

        fEnvironmentVarList.add(new EnvironmentVar(name, value));
    }

    /**
     * 
     * @return
     */
    public String getCommandOutput() {
        return fCmdOutput.toString();
    }

    /**
     * 
     * @return
     */
    public String getCommandError() {
        return fCmdError.toString();
    }

    /**
     * 
     * @param commandLine
     * @return
     */
    public boolean runCommand(String commandLine) {
        try {
            /* run command */

            Process process = runCommandHelper(commandLine);

            /* start output and error read threads */
            startOutputAndErrorReadThreads(process.getInputStream(), process.getErrorStream());

            /* wait for command execution to terminate */
            int exitStatus = -1;
            try {
                exitStatus = process.waitFor();

            } catch (Throwable ex) {
                return false;
                //throw new Exception(ex.getMessage());
            } finally {
                /* notify output and error read threads to stop reading */
                notifyOutputAndErrorReadThreadsToStopReading();
            }

            if (exitStatus == -1) {
                return false;
            }
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 
     * @param commandLine
     * @return
     * @throws IOException 
     */
    private Process runCommandHelper(String commandLine) throws IOException {
        Process process = null;
        if (fWorkingDirectory == null) {
            process = Runtime.getRuntime().exec(commandLine, getEnvTokens());
        } else {
            process = Runtime.getRuntime().exec(commandLine, getEnvTokens(), new File(fWorkingDirectory));
        }

        return process;
    }

    /**
     * 
     * @param processOut
     * @param processErr 
     */
    private void startOutputAndErrorReadThreads(InputStream processOut, InputStream processErr) {
        fCmdOutput = new StringBuffer();
        fCmdOutputThread = new AsyncStreamReader(processOut, fCmdOutput, fOuputLogDevice, "OUTPUT");
        fCmdOutputThread.start();

        fCmdError = new StringBuffer();
        fCmdErrorThread = new AsyncStreamReader(processErr, fCmdError, fErrorLogDevice, "ERROR");
        fCmdErrorThread.start();
    }

    /**
     * 
     */
    private void notifyOutputAndErrorReadThreadsToStopReading() {
        fCmdOutputThread.stopReading();
        fCmdErrorThread.stopReading();
    }

    /**
     * 
     * @return 
     */
    private String[] getEnvTokens() {
        if (fEnvironmentVarList == null) {
            return null;
        }

        String[] envTokenArray = new String[fEnvironmentVarList.size()];
        Iterator envVarIter = fEnvironmentVarList.iterator();
        int nEnvVarIndex = 0;
        while (envVarIter.hasNext() == true) {
            EnvironmentVar envVar = (EnvironmentVar) (envVarIter.next());
            String envVarToken = envVar.fName + "=" + envVar.fValue;
            envTokenArray[nEnvVarIndex++] = envVarToken;
        }

        return envTokenArray;
    }
}

/**
 * 
 * @author Qing An - qinga@kth.se
 * Condroid Summer CSD 2011 - KTH
 */
class AsyncStreamReader extends Thread {

    private StringBuffer fBuffer = null;
    private InputStream fInputStream = null;
    private String fThreadId = null;
    private boolean fStop = false;
    private ILogDevice fLogDevice = null;
    private String fNewLine = null;

    /**
     * 
     * @param inputStream
     * @param buffer
     * @param logDevice
     * @param threadId 
     */
    public AsyncStreamReader(InputStream inputStream, StringBuffer buffer, ILogDevice logDevice, String threadId) {
        fInputStream = inputStream;
        fBuffer = buffer;
        fThreadId = threadId;
        fLogDevice = logDevice;

        fNewLine = System.getProperty("line.separator");
    }

    /**
     * 
     * @return 
     */
    public String getBuffer() {
        return fBuffer.toString();
    }

    /**
     * 
     */
    public void run() {
        try {
            readCommandOutput();
        } catch (Exception ex) {
            //ex.printStackTrace(); //DEBUG
        }
    }

    /**
     * 
     * @throws IOException 
     */
    private void readCommandOutput() throws IOException {
        BufferedReader bufOut = new BufferedReader(new InputStreamReader(fInputStream));

        char c = '\0';
        int charInt;
        while ((fStop == false) && ((charInt = bufOut.read()) != -1)) {
            c = (char) charInt;
            fBuffer.append(c);
            printToDisplayDevice(c);
        }
        bufOut.close();
        //printToConsole("END OF: " + fThreadId); //DEBUG
    }

    /**
     * 
     */
    public void stopReading() {
        fStop = true;
    }

    /**
     * 
     * @param c 
     */
    private void printToDisplayDevice(char c) {
        if (fLogDevice != null) {
            fLogDevice.log(c);
        } else {
            //printToConsole(c);//DEBUG
        }
    }

    /**
     * 
     * @param c 
     */
    private synchronized void printToConsole(char c) {
        System.out.print(c);
    }
}

/**
 * 
 * @author Qing An - qinga@kth.se
 * Condroid Summer CSD 2011 - KTH
 */
class EnvironmentVar {

    public String fName = null;
    public String fValue = null;

    public EnvironmentVar(String name, String value) {
        fName = name;
        fValue = value;
    }
}
